package com.seednia.module.core.tagging.dto;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import javax.persistence.Transient;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;


/**
 * Data type that contains taggings of multiple types.
 * The tags can be accessed by by providing the tagType
 * 
 * @author Young Suk Ahn
 */
@XmlRootElement(name = "TaggingCollection", 
		namespace = "http://seednia.com/core/1.0")
@XmlType(name = "TaggingCollection")
@XmlAccessorType(XmlAccessType.PROPERTY)
public class TaggingCollectionContainer {

	private ITaggable taggable = null;
	
	private Map<String, TypedTaggingCollection> taggingCollectionMap = new HashMap<String, TypedTaggingCollection>();

	public TaggingCollectionContainer()
	{}
	
	public TaggingCollectionContainer(ITaggable taggable)
	{
		this.taggable = taggable;
		for (String tagType: taggable.getTagTypes())  {
			addTagType(tagType);
		}
	}
	
	/**
	 * Add a tag type entry along with the tagging collection which will hold all the tags
	 * this particular type (e.g. "Post")
	 * @param tagType
	 */
	public final void addTagType(String tagType)
	{
		taggingCollectionMap.put(tagType, new TypedTaggingCollection(this, tagType));
	}
	
	public final TypedTaggingCollection getTaggingCollection(String type)
	{
		TypedTaggingCollection tc = taggingCollectionMap.get(type);
		if (tc == null)
			throw new IllegalArgumentException("Type "+type+" not found");
		return tc;
	}
	public final TypedTaggingCollection getOrCreateTaggingCollection(String type)
	{
		TypedTaggingCollection tc = null;
		if (!this.containsType(type)) {
			tc = new TypedTaggingCollection(this, type);
			this.taggingCollectionMap.put(type, tc);
		} else {
			tc = getTaggingCollection(type);
		}
		return tc;
	}
	
	public final boolean containsType(String type)
	{
		return taggingCollectionMap.containsKey(type);
	}
	
	/**
	 * Checks wheter the tag is already.
	 * @param tagName
	 * @return
	 */
	public boolean containsTag(String type, String tagName)
	{
		return getTaggingCollection(type).containsTag(tagName);
	}

	/**
	 * Returns a tag dto given the name
	 * @param tagName
	 * @return
	 */
	public TaggingDto getTaggingDto(String type, String tagName) {
		return getTaggingCollection(type).getTaggingDto(tagName);
	}
	public Collection<TaggingDto> getTaggingDtos(String type) {
		return getTaggingCollection(type).getTaggingDtos();
	}
	public void setTaggingDtos(String type, Collection<TaggingDto> taggingDtos) {
		getOrCreateTaggingCollection(type).setTaggingDtos(taggingDtos);
	}
	public void addTaggingDto(String type, TaggingDto taggingDto) {
		getOrCreateTaggingCollection(type).addTaggingDto(taggingDto);
	}
	public void removeTaggingDto(String type, TaggingDto taggingDto) {
		getTaggingCollection(type).removeTaggingDto(taggingDto);
	}
	
	@Transient
	public TagDto getTagDto(String type, String tagName) {
		return getTaggingCollection(type).getTagDto(tagName);
	}
	
	public Collection<TagDto> getTagDtos(String type) {
		return getTaggingCollection(type).getTagDtos();
	}
	
	public String[] getTagNames(String type) {
		return getTaggingCollection(type).getTagNames();
	}
	public void setTagNames(String type, String[] tagNames) {
		getOrCreateTaggingCollection(type).setTagNames(tagNames);
	}
	
	public String getTagNamesCsv(String type)
	{
		return getTaggingCollection(type).getTagNamesCsv();
	}

	public void setTagNamesCsv(String type, String tagNamesCsv)
	{
		getOrCreateTaggingCollection(type).setTagNamesCsv(tagNamesCsv);
	}
	
	protected final String extractKey(String type, TaggingDto taggingDto)
	{
		return getTaggingCollection(type).extractKey(taggingDto);
	}
	
	/**
	 * Data structure that contains a collection of tags of a single type. E.g. 'Skill'
	 * @author Young Suk Ahn
	 *
	 */
	public class TypedTaggingCollection {
		
		private TaggingCollectionContainer container = null;
		private String type = null;
		private LinkedHashMap<String, TaggingDto> taggingDtos = null;

		
		public TypedTaggingCollection(TaggingCollectionContainer container, String type)
		{
			this.container = container;
			this.type = type;
		}
				
		
		public TaggingCollectionContainer getContainer() {
			return container;
		}
		public void setContainer(TaggingCollectionContainer container) {
			this.container = container;
		}
		public String getType() {
			return type;
		}
		public void setType(String type) {
			this.type = type;
		}

		/**
		 * Checks whether the tag is already.
		 * @param tagName
		 * @return
		 */
		public boolean containsTag(String tagName)
		{
			return taggingDtos.containsKey(tagName);
		}

		/**
		 * Returns a tag dto given the name
		 * @param tagName
		 * @return
		 */
		public TaggingDto getTaggingDto(String tagName) {
			if (this.taggingDtos == null || !this.taggingDtos.containsKey(tagName))
				return null;
			
			return this.taggingDtos.get(tagName);
		}
		public Collection<TaggingDto> getTaggingDtos() {
			if (taggingDtos == null)
				return null;
			return taggingDtos.values();
		}
		public void setTaggingDtos(Collection<TaggingDto> taggingDtos) {
			if (taggingDtos == null)
				return;
			if (this.taggingDtos == null)
				this.taggingDtos = new LinkedHashMap<String, TaggingDto>();
			for(TaggingDto taggingDto: taggingDtos) {
				// [12/05/06] Doubt about this..
				if (taggingDto.getTagDto() != null)
					this.taggingDtos.put(extractKey(taggingDto), taggingDto);
			}
		}
		public void addTaggingDto(TaggingDto taggingDto) {
			if (taggingDto == null)
				return;
			if (this.taggingDtos == null)
				this.taggingDtos = new LinkedHashMap<String, TaggingDto>();
			this.taggingDtos.put(extractKey(taggingDto), taggingDto);
		}
		public void removeTaggingDto(TaggingDto taggingDto) {
			if (this.taggingDtos == null || taggingDto == null)
				return;
			this.taggingDtos.remove(extractKey(taggingDto));
		}
		
		@Transient
		public TagDto getTagDto(String tagName) {
			if (this.taggingDtos == null || !this.taggingDtos.containsKey(tagName))
				return null;
			
			return getTaggingDto(tagName).getTagDto();
		}
		
		public Collection<TagDto> getTagDtos() {
			if (this.taggingDtos == null)
				return null;
			List<TagDto> retval = new ArrayList<TagDto>();
			for(TaggingDto taggingDto: this.taggingDtos.values()) {
				retval.add(taggingDto.getTagDto());
			}
			return retval;
		}
		
		public String[] getTagNames() {
			if (this.taggingDtos == null)
				return null;
			String[] retval = new String[this.taggingDtos.size()];
			int i=0;
			for(TaggingDto taggingDto: this.taggingDtos.values()) {
				retval[i++] = taggingDto.getTagDto().getName();
			}
			return retval;
		}
		public void setTagNames(String[] tagNames) {
			for (String tagName: tagNames)
				this.addTaggingDto(new TaggingDto(this.getContainer().taggable, this.getType(), new TagDto(tagName)));
		}
		
		public String getTagNamesCsv()
		{
			String[] tagNames = getTagNames();
			if (tagNames == null)
				return null;
			StringBuffer tagNamesCsv = new StringBuffer();
			for(String tagName: tagNames)
				tagNamesCsv.append(tagName).append(",");
			if (tagNamesCsv.length() > 1)
				tagNamesCsv.deleteCharAt(tagNamesCsv.length()-1);
			return tagNamesCsv.toString();
		}

		public void setTagNamesCsv(String tagNamesCsv)
		{
			if (tagNamesCsv.trim().length()==0)
				return;
			String[] tagNames = tagNamesCsv.split(",");
			setTagNames(tagNames);
		}
		
		protected final String extractKey(TaggingDto taggingDto)
		{
			return taggingDto.getTagDto().getName();
		}
	}
	
}
